package com.ljk.util;

import com.ljk.util.date.DateConstant;
import com.ljk.util.date.LocalDateUtil;
import com.ljk.util.string.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;

/**
 * 身份证本地验证工具类
 *
 * @author ljk
 * @since 2021/09/16
 */
public class IdCardUtil {

    private static final Logger log = LoggerFactory.getLogger(IdCardUtil.class);

    /** 一代身份证长度 */
    public static final int ID_CARD_LENGTH_ONE = 15;
    /** 二代身份证长度 */
    public static final int ID_CARD_LENGTH_TWO = 18;
    /** 二代身份证系数 */
    private static final int[] COEFFICIENT_ARRAY = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
    /** 二代身份证校验码对照表 */
    private static final char[] CHECK_CODE_ARRAY = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};

    /**
     * 验证中华人民共和国身份证号码是否正确
     *
     * @param cardNo 待验证的身份证号，不能为空
     * @return boolean 合法返回true，不合法返回false
     * @author ljk
     * @since 2021/09/16
     */
    public static boolean idCardValid(String cardNo) {
        if (StringUtil.isEmpty(cardNo)) {
            return false;
        }
        try {
            switch (cardNo.length()) {
                case ID_CARD_LENGTH_TWO:
                    // 提取前17位数字
                    String prefix = cardNo.substring(0, ID_CARD_LENGTH_TWO - 1);
                    // 提取最后一位校验码
                    String checkDigit = cardNo.substring(ID_CARD_LENGTH_TWO - 1);
                    // 校验前17位是否全为数字，并且计算校验码是否匹配
                    if (!prefix.matches("\\d{17}")) {
                        return false;
                    }
                    char[] prefixChars = prefix.toCharArray();
                    return checkDigit.equalsIgnoreCase(calcCheckDigit(prefixChars));
                case ID_CARD_LENGTH_ONE:
                    //一代身份证校验
                    return validateIdCard15(cardNo);
                default:
                    return false;
            }
        } catch (NumberFormatException e) {
            log.warn("Invalid number format in ID card validation for cardNo: {}", cardNo);
            return false;
        } catch (Exception e) {
            log.error("Error occurred during ID card validation for cardNo: {}", cardNo, e);
            return false;
        }
    }

    /**
     * 计算18位身份证的第18位校验码
     * <p>
     * 根据GB 11643-1999标准，公民身份号码由十七位数字本体码和一位数字校验码组成。
     * 排列顺序从左至右依次为：六位数字地址码，八位数字出生日期码，
     * 三位数字顺序码和一位数字校验码。
     * </p>
     * <p>
     * 校验码计算方法：
     * 1. 将前面的身份证号码17位数分别乘以不同的系数。
     * 从第一位到第十七位的系数分别为：7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2
     * 2. 将这17位数字和系数相乘的结果相加。
     * 3. 用加出来的和除以11，得到余数。
     * 4. 余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。
     * 其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2。
     * </p>
     *
     * @param prefixChars 身份证号前17位组成的字符数组
     * @return 第18位校验码字符
     * @throws IllegalArgumentException 如果输入数组长度不足17
     */
    private static String calcCheckDigit(char[] prefixChars) throws IllegalArgumentException {
        if (prefixChars.length != 17) {
            throw new IllegalArgumentException("Input array must contain exactly 17 characters.");
        }
        int sum = 0;
        for (int i = 0; i < 17; i++) {
            // 已经在校验处确保是数字，这里可以直接转换
            int digit = Character.getNumericValue(prefixChars[i]);
            sum += COEFFICIENT_ARRAY[i] * digit;
        }
        int remainder = sum % 11;
        return String.valueOf(CHECK_CODE_ARRAY[remainder]);
    }

    /**
     * 校验15位一代身份证号码
     *
     * @param idCard 待校验的15位身份证号
     * @return boolean 合法返回true，不合法返回false
     */
    private static boolean validateIdCard15(String idCard) {
        // 1. 检查是否全为数字
        if (!idCard.matches("\\d{15}")) {
            log.debug("15-digit ID card validation failed: Not all digits for cardNo: {}", idCard);
            return false;
        }
        // 2. 提取出生日期部分 (第7位到第12位)
        String datePart = idCard.substring(6, 12);
        // 3. 校验日期有效性 (假设是19XX年)
        // 使用 STRICT 解析风格来确保日期有效（例如，不允许 2月30日）
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd").withResolverStyle(ResolverStyle.STRICT);
        try {
            formatter.parse("19" + datePart);
        } catch (DateTimeParseException e) {
            log.debug("15-digit ID card validation failed: Invalid date format for cardNo: {}", idCard, e);
            return false;
        }
        // 4. 如果以上校验都通过，则认为有效
        // 注意：一代身份证没有像二代那样的校验码算法，主要依赖日期有效性
        return true;
    }


    /**
     * 依据有效的身份证号计算生日
     *
     * @param idNo 有效的身份证号
     * @return 生日 LocalDate 对象，如果身份证无效则返回 null
     * @author ljk
     * @since 2023/02/21
     */
    public static LocalDate calcBirthdayByIdNo(String idNo) {
        if (StringUtil.isEmpty(idNo)) {
            return null;
        }
        try {
            String birthDateString = idNo.substring(6, 14);
            return LocalDateUtil.parseLocalDate(birthDateString, DateConstant.YYYYMMDD);
        } catch (Exception e) {
            log.warn("Failed to parse birthday from ID card number: {}", idNo, e);
            return null;
        }
    }

    /**
     * 依据有效的身份证号计算性别
     *
     * @param idNo 有效的身份证号
     * @return 性别代码："1"表示男性，"2"表示女性；如果身份证无效则返回 null
     * @author ljk
     * @since 2023/02/21
     */
    public static String calcSexByIdNo(String idNo) {
        if (StringUtil.isEmpty(idNo)) {
            return null;
        }
        try {
            // 男1女2 - 第17位（索引16）奇数为男，偶数为女
            String sexDigit = idNo.substring(16, 17);
            int sexNumber = Integer.parseInt(sexDigit);
            return (sexNumber % 2) == 1 ? "1" : "2";
        } catch (Exception e) {
            log.error("Failed to parse gender from ID card number: {}", idNo, e);
            throw new UtilException("从身份证号解析性别失败,身份证号:" + idNo, e);
        }
    }
}
